package com.example.controller;

import com.example.domain.po.LoginUserDetails;
import com.example.domain.vo.Result;
import com.example.utils.JwtUtils;
import com.example.utils.RedisClient;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@Slf4j
@RequiredArgsConstructor
@RestController
public class LoginController {


    private final ObjectMapper objectMapper;
    private final AuthenticationManager authenticationManager;//认证管理器
    private final RedisClient redisClient;//redis客户端
    /**
     * 登录
     * @param username
     * @param password
     * @return token
     */
    @PostMapping("/login")
    public Result login(String username, String password, HttpServletRequest request) {
        log.info("用户登录{},{}",username,password);
        /*
         * 登录前判断是否上次的登录未过期,如果未过期直接删除,重新登录生成新token
         * (用户携带token登录)
         */
        String token_ = request.getHeader("token");
        //判断是否存在token
        if(StringUtils.hasText(token_)){
            //解析 token,获得用户名
            String claim = JwtUtils.getClaim(token_);
            if(StringUtils.hasText(claim)){
                //校验是否是同一个账户
                if(username.equals(claim)){
                    //同一个账户,redis删除原来的token
                    String key="login:token:"+token_;
                    redisClient.del(key);
                    //重新生成token,保存到redis中,返回给前端
                }
            }
        }
        /*
         * SpringSecurity 的认证逻辑
         * 默认情况SpringSecurity内置了登录页面,内置了从页面获取数据,并将其数据送到SpringSecurity上下文的方式
         * 当前前后端分离的逻辑，数据不再从页面获取，所以不能再使用内置逻辑，需要程序员自己实现将数据送到SpringSecurity上下文中
         * 官网地址: https://docs.spring.io/spring-security/reference/5.8/servlet/authentication/passwords/form.html
         */
        //封装用户名和密码
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username,password);
        //调用认证管理中的认证方法，调用后可能出现异常，所以需要try...catch
        try {
            Authentication authenticate = authenticationManager.authenticate(authenticationToken);
            //如果认证成功Authentication中就会有用户信息,否则为空
            if(Objects.isNull(authenticate)){
                //认证失败
                return Result.error("认证失败,用户名或密码错误");
            }

            //生成 token
            String token = JwtUtils.sign(username, Long.valueOf(1000*60*60*24*7));//7天
            //token 保存到 redis 中
            String key = "login:token:"+token;
            //用户信息json化
            LoginUserDetails principal = (LoginUserDetails) authenticate.getPrincipal();
            String json = null;
            try {
                json = objectMapper.writeValueAsString(principal);
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
            redisClient.set(key,json, Long.valueOf(1000*60*60*24*7)); //7天
            //将token返回给客户端
            Map<String,Object> map = new HashMap<>();
            map.put("token",token);
            return Result.ok(map);
        }catch (RuntimeException e){
            e.printStackTrace();
            //认证失败
            return Result.error("认证失败,用户名或密码错误");
        }

    }
}
